/**
 * Copyright (c) 2019-2029, wwww.kiwipeach.cn (liuburu@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package open.kiwipeach.autoconfigure.web.filter.xss;

import cn.kiwipeach.blog.exception.BlogException;
import lombok.extern.slf4j.Slf4j;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 描述：xss辅助工具类
 *
 * @author kiwipeach
 * @since 1.0
 */
@Slf4j
public class XssHelper {
    /**
     * xss过滤正则表达式
     */
    private static List<Pattern> patternList = new ArrayList<>();

    static {
        // Avoid anything between script tags
        patternList.add(Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE));
        // Avoid anything in a src='...' type of expression
        patternList.add(Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
        patternList.add(Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
        // Remove any lonesome </script> tag
        patternList.add(Pattern.compile("</script>", Pattern.CASE_INSENSITIVE));
        // Remove any lonesome <script ...> tag
        patternList.add(Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
        // Avoid eval(...) expressions
        patternList.add(Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
        // Avoid expression(...) expressions
        patternList.add(Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
        // Avoid javascript:... expressions
        patternList.add(Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE));
        // Avoid onload= expressions
        patternList.add(Pattern.compile("onload(.*?)=",
                Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
        // Avoid iframe
        patternList.add(Pattern.compile("<iframe>(.*?)</iframe>", Pattern.CASE_INSENSITIVE));
        // Remove any lonesome <script ...> tag
        patternList.add(Pattern.compile("</iframe>", Pattern.CASE_INSENSITIVE));
        patternList.add(Pattern.compile("<iframe(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
    }


    /**
     * 对一些特殊字符进行转义
     */
    public static String htmlEncode(String aText) {
        final StringBuilder result = new StringBuilder();
        final StringCharacterIterator iterator = new StringCharacterIterator(aText);
        char character = iterator.current();
        while (character != CharacterIterator.DONE) {
            if (character == '<') {
                result.append("＜");
            } else if (character == '>') {
                result.append("＞");
            } else if (character == '\"') {
                result.append("｀");
            } else {
                // the char is not a special one
                // add it to the result as is
                result.append(character);
            }
            character = iterator.next();
        }
        return result.toString();
    }

    /**
     * 将容易引起xss漏洞的半角字符直接替换成全角字符
     * 目前xssProject对注入代码要求是必须开始标签和结束标签(如<script></script>)正确匹配才能解析，否则报错；因此只能替换调xssProject换为自定义实现
     *
     * @param s
     * @return
     */
    public static String xssEncode(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }
        String result = stripXss(s);
        if (null != result) {
            result = escape(result);
        }

        return result;

    }

    private static String escape(String s) {
        StringBuilder sb = new StringBuilder(s.length() + 16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '>':
                    // 全角大于号
                    sb.append('＞');
                    break;
                case '<':
                    // 全角小于号
                    sb.append('＜');
                    break;
                case '\'':
                    // 全角单引号
                    sb.append('‘');
                    break;
                case '\"':
                    // 全角双引号
                    sb.append('“');
                    break;
                case '\\':
                    // 全角斜线
                    sb.append('＼');
                    break;
                case '%':
                    // 全角冒号
                    sb.append('％');
                    break;
                default:
                    sb.append(c);
                    break;
            }

        }
        return sb.toString();
    }


    private static String stripXss(String value) {
        if (value != null) {
            value = value.replaceAll("", "");
            for (Pattern scriptPattern : patternList) {
                scriptPatternExceptionCheck(scriptPattern, value);
                value = scriptPattern.matcher(value).replaceAll("");
            }
        }
        return value;
    }

    private static void scriptPatternExceptionCheck(Pattern scriptPattern, String value) {
        if (scriptPattern.matcher(value).find()) {
            log.warn("系统检测到疑似非法xss攻击:scriptPattern={},value={}", scriptPattern.pattern(), value);
            throw new BlogException("-XSS_001", "系统检测到您正在进行非法的xss攻击，如果系统判断有误，请联系统管理员。");
        }
    }

}
